/**
 * @Time: 2023/5/27 16:09
 * @File: app.go
 * @Software: GoLand
 * @Github: https://github.com/yhy0
 * @Contact: chenyihang110@gmail.com
 * @Project: edu
 * @Description: app应用程序服务器
 */

package app

import (
	"context"
	"crypto/tls"
	"errors"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"

	"learning.com/edu/app/api"
	"learning.com/edu/models"
	"learning.com/edu/utils/helper"
	"learning.com/edu/utils/jsonlog"
)

// 应用程序
type Application struct {
	config *models.Config
	helper *helper.Helper
	server *http.Server
	videoServer *http.Server
	api    *api.Api
}

// 创建服务
func New(configPath, currentDir string) (*Application, error) {
	// 创建配置
	config, err := models.NewConfig(configPath, currentDir)
	if err != nil {
		return nil, err
	}

	// 创建logger
	prettyLog := config.Env == "dev"
	logger    := jsonlog.New(os.Stdout, jsonlog.LevelInfo, prettyLog)
	helper 	  := helper.New(logger)
	api, err  := api.New(helper, config)
	if err != nil {
		return nil, err
	}

	// 创建api
	return &Application{
		config: config,
		helper: helper,
		api: 	api,
	}, nil
}

// 启动服务
func (app *Application) runServer() error {
	// 创建tls配置
	tlsCfg := &tls.Config{
		CurvePreferences: []tls.CurveID{tls.X25519, tls.CurveP256},
		MinVersion:   tls.VersionTLS12,
	}

	// 创建服务
	addr := fmt.Sprintf("%s:%d", app.config.Server, app.config.Port)
	app.server = &http.Server{
		Addr:      addr,
		Handler:   app.api.Routes(),
		ErrorLog:  log.New(app.helper.Logger, "", 0),
		TLSConfig: tlsCfg,

		// 配置超时
		IdleTimeout: time.Minute,
		ReadTimeout: 10 * time.Second,
		WriteTimeout: 30 * time.Second,
		MaxHeaderBytes: 8192,
	}

	app.helper.Logger.Info("服务器启动", map[string]string{
		"addr": addr,
		"env": app.config.Env,
	})

	// 启动服务
	err := app.server.ListenAndServeTLS(app.config.Path.CertPath(), app.config.Path.KeyPath())
	if !errors.Is(err, http.ErrServerClosed) {
		return err
	} else {
		app.helper.Logger.Info("停止服务器", map[string]string{"addr": addr})
		return nil
	}
}

// 启动视频服务
func (app *Application) runVideoServer() error {
	addr := fmt.Sprintf("%s:%d", app.config.Server, app.config.VideoPort)
	app.videoServer = &http.Server{
		Addr:      addr,
		Handler:   app.api.CourseVideoRoutes(),
		ErrorLog:  log.New(app.helper.Logger, "", 0),

		// 配置超时
		IdleTimeout: time.Minute,
		ReadTimeout: 10 * time.Second,
		WriteTimeout: 30 * time.Second,
		MaxHeaderBytes: 8192,
	}

	app.helper.Logger.Info("视频服务启动", map[string]string{
		"addr": addr,
		"env": app.config.Env,
	})

	err := app.videoServer.ListenAndServe()
	if !errors.Is(err, http.ErrServerClosed) {
		return err
	} else {
		app.helper.Logger.Info("停止视频服务", map[string]string{"addr": addr})
		return nil
	}
}

// 运行
func (app *Application) Run() error {
	shutdownError := make(chan error)

	// 启动服务
	go func() {
		err := app.runServer()
		if err != nil {
			shutdownError <- err
		}
	}()

	// 启动视频服务
	go func() {
		err := app.runVideoServer()
		if err != nil {
			shutdownError <- err
		}
	}()

	// 监听信号
	go func() {
		// 监听信号
		quit := make(chan os.Signal, 1)
		signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
		s := <-quit
		
		// 停止服务
		app.helper.Logger.Info("shutting down server", map[string]string{
			"signal": s.String(),
		})

		// 创建一个超时
		ctx, cancel := context.WithTimeout(context.Background(), 20 * time.Second)
		defer cancel()

		// 关闭视频服务
		err := app.videoServer.Shutdown(ctx)
		if err != nil {
			shutdownError <- err
		}

		// 停止服务
		err = app.server.Shutdown(ctx)
		if err != nil {
			shutdownError <- err
		}
		app.helper.Logger.Info("completing admin tasks", map[string]string{
			"addr": app.server.Addr,
		})
		// 等待后台任务完成
		app.helper.Admin.Wait()
		shutdownError <- nil
	}()
	// 等待信号
	err := <- shutdownError
	return err
}
